<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>2. CSS 过渡与动画 | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/152.b2c6e829.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link router-link-active">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link router-link-active">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>9102 全员学 Vue</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>深入理解 Vue 动画效果</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/vue/vue-transition/1-transition-component.html" class="sidebar-link">1. transition组件</a></li><li><a href="/front-end-playground/vue/vue-transition/2-css-transition-animation.html" aria-current="page" class="active sidebar-link">2. CSS 过渡与动画</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/vue/vue-transition/2-css-transition-animation.html#css-transition" class="sidebar-link">CSS transition</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/vue/vue-transition/2-css-transition-animation.html#vue-与-css-transition" class="sidebar-link">Vue 与 CSS transition</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/vue/vue-transition/2-css-transition-animation.html#css-animation" class="sidebar-link">CSS animation</a></li></ul></li><li><a href="/front-end-playground/vue/vue-transition/3-javascript-hook.html" class="sidebar-link">3. Javascript钩子</a></li><li><a href="/front-end-playground/vue/vue-transition/4-multi-element.html" class="sidebar-link">4. 多元素/组件过渡</a></li><li><a href="/front-end-playground/vue/vue-transition/5-list-and-flip.html" class="sidebar-link">5. FLIP 与列表过渡</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>Webpack 实现 Vue 多页应用</span> <span class="arrow right"></span></p> <!----></section></li><li><a href="http://www.godbasin.com/vue-ebook/" target="_blank" rel="noopener noreferrer" class="sidebar-link">深入理解Vue.js与实战<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>该系列用于记录一些使用方法、demo 以及原理分析。本文在 Vue 中使用 CSS 过渡、 CSS 动画的方式来实现动画效果。</p> <h1 id="css-过渡与动画"><a href="#css-过渡与动画" class="header-anchor">#</a> CSS 过渡与动画</h1> <p>来，我们先认识下 CSS 过渡。</p> <h2 id="css-transition"><a href="#css-transition" class="header-anchor">#</a> CSS transition</h2> <p>CSS transitions 提供了一种在更改 CSS 属性时控制动画速度的方法。 其可以让属性变化成为一个持续一段时间的过程，而不是立即生效的。比如，将一个元素的颜色从白色改为黑色，通常这个改变是立即生效的，使用 CSS transitions 后该元素的颜色将逐渐从白色变为黑色，按照一定的曲线速率变化。这个过程可以自定义。</p> <p>通常将两个状态之间的过渡称为隐式过渡（implicit transitions），因为开始与结束之间的状态由浏览器决定。</p> <h3 id="transition-使用"><a href="#transition-使用" class="header-anchor">#</a> transition 使用</h3> <p>CSS transitions 可以决定哪些属性发生动画效果 (明确地列出这些属性)，何时开始 (设置 delay），持续多久 (设置 duration) 以及如何动画 (定义 timing funtion，比如匀速地或先快后慢)。</p> <p>CSS 过渡 由简写属性 transition 定义是最好的方式：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">div</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> &lt;property&gt; &lt;duration&gt; &lt;timing-function&gt; &lt;delay&gt;<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><h4 id="property"><a href="#property" class="header-anchor">#</a> property</h4> <p>指定哪个或哪些 CSS 属性用于过渡。只有指定的属性才会在过渡中发生动画，其它属性仍如通常那样瞬间变化。all 则为全部属性。</p> <h4 id="duration"><a href="#duration" class="header-anchor">#</a> duration</h4> <p>指定过渡的时长。或者为所有属性指定一个值，或者指定多个值，为每个属性指定不同的时长。</p> <h4 id="timing-function"><a href="#timing-function" class="header-anchor">#</a> timing-function</h4> <p>指定一个函数，定义属性值怎么变化。常用如 linear、ease，更多可参考<a href="http://easings.net/zh-cn" target="_blank" rel="noopener noreferrer">缓动函数<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。</p> <h4 id="delay"><a href="#delay" class="header-anchor">#</a> delay</h4> <p>指定延迟，即属性开始变化时与过渡开始发生时之间的时长。</p> <p>下面的简写和不简写的例子：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">.short-for-transition</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> margin-left 4s ease 2s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.transition-detail</span> <span class="token punctuation">{</span>
  <span class="token property">transition-property</span><span class="token punctuation">:</span> margin-left<span class="token punctuation">;</span>
  <span class="token property">transition-duration</span><span class="token punctuation">:</span> 4s<span class="token punctuation">;</span>
  <span class="token property">transition-timing-function</span><span class="token punctuation">:</span> ease<span class="token punctuation">;</span>
  <span class="token property">transition-delay</span><span class="token punctuation">:</span> 2s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>关于每一个属性的详情，可以参考<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions" target="_blank" rel="noopener noreferrer">CSS transition | MDN<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。</p> <h2 id="vue-与-css-transition"><a href="#vue-与-css-transition" class="header-anchor">#</a> Vue 与 CSS transition</h2> <h3 id="简单的-demo"><a href="#简单的-demo" class="header-anchor">#</a> 简单的 demo</h3> <p>上面我们已经大概讲了 CSS transition 的使用方式，这里与 Vue 的结合也变得很简单，我们还是看官方的例子：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- transition的使用 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>demo<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show = !show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    Toggle
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>transition</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fade<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>hello<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>transition</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  el<span class="token operator">:</span> <span class="token string">&quot;#demo&quot;</span><span class="token punctuation">,</span>
  data<span class="token operator">:</span> <span class="token punctuation">{</span>
    show<span class="token operator">:</span> <span class="token boolean">true</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><div class="language-css extra-class"><pre class="language-css"><code><span class="token comment">/* 简单的css transition实现动画 */</span>
<span class="token selector">.fade-enter-active,
.fade-leave-active</span> <span class="token punctuation">{</span>
  <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.5s<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
.fade-enter<span class="token punctuation">,</span> .fade-leave-to <span class="token comment">/* .fade-leave-active below version 2.1.8 */</span> <span class="token punctuation">{</span>
  <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="过渡的类名"><a href="#过渡的类名" class="header-anchor">#</a> 过渡的类名</h3> <p>我们在 transition 上设置了 name 的值为 fade，然后效果是 Vue 匹配对应的 fade-status 的类名。我们先来看看 Vue 里面提供了哪些类名：</p> <ul><li><strong>v-enter</strong>：定义进入过渡的开始状态。
<ul><li>在元素被插入时生效，在下一个帧移除。</li></ul></li> <li><strong>v-enter-active</strong>：定义过渡的状态。
<ul><li>在元素整个过渡过程中作用，在元素被插入时生效，在 transition/animation 完成之后移除。</li> <li>这个类可以被用来定义过渡的过程时间，延迟和曲线函数。</li></ul></li> <li><strong>v-enter-to</strong>: (2.1.8 版及以上)定义进入过渡的结束状态。
<ul><li>在元素被插入一帧后生效 (与此同时 v-enter 被删除)，在 transition/animation 完成之后移除。</li></ul></li> <li><strong>v-leav</strong>e: 定义离开过渡的开始状态。
<ul><li>在离开过渡被触发时生效，在下一个帧移除。</li></ul></li> <li><strong>v-leave-active</strong>：定义过渡的状态。
<ul><li>在元素整个过渡过程中作用，在离开过渡被触发后立即生效，在 transition/animation 完成之后移除。</li> <li>这个类可以被用来定义过渡的过程时间，延迟和曲线函数。</li></ul></li> <li><strong>v-leave-to</strong>: (2.1.8 版及以上)定义离开过渡的结束状态。
<ul><li>在离开过渡被触发一帧后生效 (与此同时 v-leave 被删除)，在 transition/animation 完成之后移除。</li></ul></li></ul> <p>文字很多，读起来有点费力气，是时候贴上官网的这个图了：</p> <p>2.1.8 版本前：
<img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/transition.png" alt="image"></p> <p>2.1.8 版及以上：
<img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/transition2.1.8.png" alt="image"></p> <h3 id="实现逻辑"><a href="#实现逻辑" class="header-anchor">#</a> 实现逻辑</h3> <p>上一节<a href="">《Vue2 动画 1--transition 组件》</a>我们简单介绍了一些实现原理相关的函数和事件，这里我们再详细分析下。</p> <p>根据上一节，我们获得以下的信息：</p> <ul><li>可以使用 requestAnimationFrame 来请求浏览器在下一次重绘之前调用指定的函数来更新动画</li> <li>当 CSS transition 结束时，会触发 transitionend 事件</li></ul> <p>所以结合之前的猜想和 Vue 的源码，能大概得到这里的实现方式：</p> <ol><li>transition 组件关注子元素是否展示，包括使用 v-if/v-else/v-for 等指令绑定数据生成的元素。</li> <li>当元素状态变更（display -&gt; none 或 none -&gt; display）时，预埋的钩子检测是否应用了 CSS 过渡。</li> <li>若使用了 CSS 过渡，则分两种情况讨论。</li></ol> <h4 id="进入动画"><a href="#进入动画" class="header-anchor">#</a> 进入动画</h4> <p>当新元素插入时，我们按照以下方式实现进入动画：</p> <ul><li>当元素插入完成后（mounted），给元素添加对应 v-enter 和 v-enter-active 类名，此时元素开始动画过渡</li> <li>同时通过 requestAnimationFrame 来指定下一帧绘制前，给元素添加 v-enter-to 类名，同时移除 v-enter 类名</li> <li>设置动画结束时间，或者通过 transitionend 事件监听，在过渡结束后，移除 v-enter-to 和 v-enter-active 类名</li></ul> <h4 id="离开动画"><a href="#离开动画" class="header-anchor">#</a> 离开动画</h4> <p>当新元素被删除时，我们按照以下方式实现离开动画：</p> <ul><li>当元素删除前（beforeDestroy），给元素添加对应 v-leave 和 v-leave-active 类名，此时元素开始动画过渡</li> <li>同时通过 requestAnimationFrame 来指定下一帧绘制前，给元素添加 v-leave-to 类名，同时移除 v-leave 类名</li> <li>设置动画结束时间，或者通过 transitionend 事件监听，在过渡结束后，移除 v-leave-to 和 v-leave-active 类名，同时执行元素删除操作</li></ul> <p>以上是整体实现方式，可见在我们使用 CSS 过渡的时候，最关键的是 v-enter-active 和 v-leave-active 两个样式。</p> <p>我们来看看 Vue 里面的部分代码：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// start enter transition</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>expectsCSS<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 添加v-enter和v-enter-active类名</span>
  <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> startClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> activeClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// nextFrame为下一帧前的执行函数</span>
  <span class="token comment">// 使用setTimeout向下兼容requestAnimationFrame</span>
  <span class="token function">nextFrame</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 添加v-enter-to类名，移除v-enter类名</span>
    <span class="token function">addTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> toClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">removeTransitionClass</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> startClass<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cb<span class="token punctuation">.</span>cancelled <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>userWantsControl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isValidDuration</span><span class="token punctuation">(</span>explicitEnterDuration<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 若获取过渡的duration成功，则通过定时器来触发结束后逻辑</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span>cb<span class="token punctuation">,</span> explicitEnterDuration<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token comment">// 若获取过渡的duration失败，则通过监听transitionend事件来触发结束后逻辑</span>
        <span class="token function">whenTransitionEnds</span><span class="token punctuation">(</span>el<span class="token punctuation">,</span> type<span class="token punctuation">,</span> cb<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当然，完整的 CSS 过渡、相关的钩子等会有更多的考虑和处理，这里只展示核心的实现逻辑部分。</p> <h2 id="css-animation"><a href="#css-animation" class="header-anchor">#</a> CSS animation</h2> <p>CSS animations 使得可以将从一个 CSS 样式配置转换到另一个 CSS 样式配置。动画包括两个部分:描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。</p> <p>创建动画序列，需要使用 animation 属性或其子属性，该属性允许配置动画时间、时长以及其他动画细节。
和 transition 不一样的是，该属性不能配置动画的实际表现，动画的实际表现是由 @keyframes 规则实现。</p> <h3 id="使用-animation"><a href="#使用-animation" class="header-anchor">#</a> 使用 animation</h3> <p>我们来看看 animation 的使用方式，其实跟 transition 很相像的，子属性如下：</p> <ul><li>animation-delay：设置延时，即从元素加载完成之后到动画序列开始执行的这段时间</li> <li>animation-direction：设置动画在每次运行完后是反向运行还是重新回到开始位置重复运行</li> <li>animation-duration：设置动画一个周期的时长</li> <li>animation-iteration-count：设置动画重复次数， 可以指定 infinite 无限次重复动画</li> <li>animation-name：指定由 @keyframes 描述的关键帧名称</li> <li>animation-play-state：允许暂停和恢复动画</li> <li>animation-timing-function：设置动画速度， 即通过建立加速度曲线，设置动画在关键帧之间是如何变化</li> <li>animation-fill-mode：指定动画执行前后如何为目标元素应用样式</li></ul> <p>同样的，CSS animation 属性也是一个简写属性形式:</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token comment">/* @keyframes duration | timing-function | delay |
   iteration-count | direction | fill-mode | play-state | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s ease-in 1s 2 reverse both paused slidein<span class="token punctuation">;</span>

<span class="token comment">/* @keyframes duration | timing-function | delay | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s linear 1s slidein<span class="token punctuation">;</span>

<span class="token comment">/* @keyframes duration | name */</span>
<span class="token property">animation</span><span class="token punctuation">:</span> 3s slidein<span class="token punctuation">;</span>
</code></pre></div><p>我们也可以用详细的子属性方式来写，这里就不详细描述啦。</p> <h3 id="关键帧动画"><a href="#关键帧动画" class="header-anchor">#</a> 关键帧动画</h3> <p>一旦完成动画的时间设置， 接下来就需要定义动画的表现。</p> <p>通过使用 @keyframes 建立两个或两个以上关键帧来实现。每一个关键帧都描述了动画元素在给定的时间点上应该如何渲染。</p> <p>因为动画的时间设置是通过 CSS 样式定义的，<strong>关键帧使用 percentage 来指定动画发生的时间点。0%表示动画的第一时刻，100%表示动画的最终时刻。<strong>因为这两个时间点十分重要，所以还有特殊的别名：<strong>from</strong>和</strong>to</strong>。</p> <p>当然，也可包含额外可选的关键帧，描述动画开始和结束之间的状态。</p> <p>来看个云在天上飘来飘去的 demo：</p> <div class="language-css extra-class"><pre class="language-css"><code><span class="token selector">.clouldmove</span> <span class="token punctuation">{</span>
  <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
  <span class="token property">width</span><span class="token punctuation">:</span> 20%<span class="token punctuation">;</span>
  <span class="token property">margin</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token property">float</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span>
  <span class="token property">margin-left</span><span class="token punctuation">:</span> 70%<span class="token punctuation">;</span>
  <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token property">animation-name</span><span class="token punctuation">:</span> ClouldMove<span class="token punctuation">;</span>
  <span class="token property">animation-duration</span><span class="token punctuation">:</span> 8s<span class="token punctuation">;</span>
  <span class="token property">animation-timing-function</span><span class="token punctuation">:</span> linear<span class="token punctuation">;</span>
  <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> infinite<span class="token punctuation">;</span>
  <span class="token property">animation-direction</span><span class="token punctuation">:</span> alternate<span class="token punctuation">;</span>
  <span class="token property">animation-play-state</span><span class="token punctuation">:</span> running<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token atrule"><span class="token rule">@keyframes</span> ClouldMove</span> <span class="token punctuation">{</span>
  <span class="token selector">0%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">25%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">50%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">75%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">100%</span> <span class="token punctuation">{</span>
    <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>还有很多的动画效果，包括轮回播放、来回运动等，这里不详细说明，大家感兴趣可以参考<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Animations/Using_CSS_animations" target="_blank" rel="noopener noreferrer">《使用 CSS 动画 | MDN》<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>。总之要记住，CSS animation 是个超级变态的动画效果实现。</p> <h3 id="animation-事件监听器"><a href="#animation-事件监听器" class="header-anchor">#</a> animation 事件监听器</h3> <p>上面我们讲到了 transitionend，animation 比它还多了些事件。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// animationstart事件在动画一开始时就被触发</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationstart&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 每个周期完成后（除了最后一个周期），会触发animationiteration事件</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationend&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 最后一个周期完成后，不会触发animationiteration事件，而触发animationend事件</span>
e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;animationiteration&quot;</span><span class="token punctuation">,</span> listener<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><h3 id="vue-与-animation"><a href="#vue-与-animation" class="header-anchor">#</a> Vue 与 animation</h3> <p>对于 Vue 来说，不管是 transition 还是 animation，都是通过 CSS 和类名来实现动画的，所以基本逻辑原理相似，除去动画结束监听的事件不一样而已。</p> <p>这里就不多描述啦。</p> <h1 id="结束语"><a href="#结束语" class="header-anchor">#</a> 结束语</h1> <p>本节我们介绍了 Vue 中组件 CSS 过渡和 CSS 动画的使用方式，以及实现逻辑的分析。很多时候，我们的工作时间限制了自己手动造轮子的能动性，但是思路的整理和部分源码的分析也能帮助我们来提升自己。</p></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/vue/vue-transition/2-css-transition-animation.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/vue/vue-transition/1-transition-component.html" class="prev">
          1. transition组件
        </a></span> <span class="next"><a href="/front-end-playground/vue/vue-transition/3-javascript-hook.html">
          3. Javascript钩子
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-9783" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty2.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/152.b2c6e829.js" defer></script>
  </body>
</html>
